/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.bef.core.session.distributed.common;

import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SessionLockProvider {

  private static ConcurrentHashMap<String, SoftReference<SessionLock>> locks = new ConcurrentHashMap<>(
      1024);

  public static Lock getDistLock(String sessionId) {
    return locks.compute(sessionId, (key, existing) -> {
      if (existing == null || existing.get() == null) {
        return new SoftReference<>(new SessionLock(key, true));
      } else {
        return existing;
      }
    }).get();
  }

  public static Lock getNonDistLock(String sessionId) {
    return locks.compute(sessionId, (key, existing) -> {
      if (existing == null || existing.get() == null) {
        return new SoftReference<>(new SessionLock(key, false));
      } else {
        return existing;
      }
    }).get();
  }

  public static void remove(String id) {
    locks.remove(id);
  }

  private static class SessionLock implements Lock {

    private final String id;
    private final boolean dist;
    private ReentrantLock appLock = new ReentrantLock(true);
    private Object sync = new Object();
    private Lock distLock;

    public SessionLock(String id, boolean dist) {
      this.id = id;
      this.dist = dist;
    }

    private Lock getDistLock() {
      if (distLock == null) {
        synchronized (sync) {
          if (distLock == null)
            distLock = new DistributedLockWrapper("bef-session-lock:".concat(id));
        }
      }
      return distLock;
    }

    @Override
    public void lock() {
      boolean heldByCurrentThread = appLock.isHeldByCurrentThread();
      appLock.lock();
      if (dist && !heldByCurrentThread) {
        try {
          getDistLock().lock();
        }catch(Throwable t) {
          appLock.unlock();
        }
      }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
      boolean heldByCurrentThread = appLock.isHeldByCurrentThread();
      appLock.lockInterruptibly();
      if (dist && !heldByCurrentThread) {
        try {
          getDistLock().lockInterruptibly();
        }catch(Throwable t) {
          appLock.unlock();
        }
      }
    }

    @Override
    public boolean tryLock() {
      boolean heldByCurrentThread = appLock.isHeldByCurrentThread();
      if (!appLock.tryLock()) {
        return false;
      }
      if (dist && !heldByCurrentThread) {
        boolean distLocked = false;
        try {
          return (distLocked = getDistLock().tryLock());
        } finally {
          if (!distLocked)
            appLock.unlock();
        }
      }
      return true;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
      boolean heldByCurrentThread = appLock.isHeldByCurrentThread();
      if (!appLock.tryLock(time, unit)) {
        return false;
      }
      if (dist && !heldByCurrentThread) {
        boolean distLocked = false;
        try {
          return (distLocked = getDistLock().tryLock(time, unit));
        } finally {
          if (!distLocked)
            appLock.unlock();
        }
      }
      return true;
    }

    @Override
    public void unlock() {
      appLock.unlock();
      if (distLock != null && !appLock.isHeldByCurrentThread()) {
        distLock.unlock();
      }
    }

    @Override
    public Condition newCondition() {
      throw new RuntimeException("not implemented");
    }

    @Override
    public String toString() {
      return "SessionLockOf[".concat(id).concat("]").concat(dist?"Dist":"NonDist");
    }
  }
}
